/*
 * $Id$
 *
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fraunhofer FOKUS Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: A lot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *

 *! \file
 *  \brief USRLOC - Usrloc contact handling functions
 *  \ingroup usrloc
 *
 * - Module: \ref usrloc
 */

#include "ucontact.h"
#include <string.h> /* memcpy */
#include "../../core/mem/shm_mem.h"
#include "../../core/ut.h"
#include "../../core/ip_addr.h"
#include "../../core/socket_info.h"
#include "../../core/dprint.h"
#include "../../lib/srdb1/db.h"
#include "ims_usrloc_scscf_mod.h"
#include "ul_callback.h"
#include "usrloc.h"
#include "impurecord.h"
#include "usrloc_db.h"
#include "../../core/hashes.h"
#include "contact_hslot.h"
#include "utime.h"

extern struct contact_list *contact_list;
extern int db_mode;

/*!
 * \brief Create a new contact structure
 * \param _dom domain
 * \param _aor address of record
 * \param _contact contact string
 * \param _ci contact informations
 * \return new created contact on success, 0 on failure
 */
ucontact_t *new_ucontact(
		str *_dom, str *_aor, str *_contact, ucontact_info_t *_ci)
{
	ucontact_t *c;
	param_t *prev = NULL;
	param_t *curr, *param;
	int first = 1;

	c = (ucontact_t *)shm_malloc(sizeof(ucontact_t));
	if(!c) {
		LM_ERR("no more shm memory\n");
		return 0;
	}
	memset(c, 0, sizeof(ucontact_t));

	c->lock = lock_alloc();
	if(c->lock == 0) {
		goto error;
	}
	if(lock_init(c->lock) == 0) {
		lock_dealloc(c->lock);
		c->lock = 0;
		goto error;
	}

	//setup callback list
	c->cbs = (struct ulcb_head_list *)shm_malloc(sizeof(struct ulcb_head_list));
	if(c->cbs == 0) {
		LM_CRIT("no more shared mem\n");
		goto error;
	}
	c->cbs->first = 0;
	c->cbs->reg_types = 0;

	/*Copy parameter list into shm**/
	param = _ci->params;
	while(param) {
		LM_DBG("Checking param [%.*s]\n", param->name.len, param->name.s);
		if(param->name.len == 16
				&& (memcmp(param->name.s, "+g.3gpp.icsi-ref", 16) == 0)) {
			c->is_3gpp = 1;
		}
		/*Copy first param in curr*/
		curr = shm_malloc(sizeof(param_t));
		curr->len = param->len;
		curr->type = param->type;
		curr->next = 0;
		if(param->body.len > 0 && param->body.s) {
			if(shm_str_dup(&curr->body, &param->body) < 0)
				goto error;
		} else {
			curr->body.s = 0;
			curr->body.len = 0;
		}
		if(param->name.len > 0 && param->name.s) {
			if(shm_str_dup(&curr->name, &param->name) < 0)
				goto error;
		} else {
			curr->name.s = 0;
			curr->name.len = 0;
		}

		if(first) {
			c->params = curr;
			first = 0;
		} else {
			prev->next = curr;
		}
		prev = curr;
		param = param->next;
	}

	if(shm_str_dup(&c->c, _contact) < 0)
		goto error;

	if(_ci->callid && _ci->callid->len > 0) {
		if(shm_str_dup(&c->callid, _ci->callid) < 0)
			goto error;
	}
	if(_ci->user_agent && _ci->user_agent->len > 0) {
		if(shm_str_dup(&c->user_agent, _ci->user_agent) < 0)
			goto error;
	}
	if(_aor && _aor->len > 0) {
		if(shm_str_dup(&c->aor, _aor) < 0)
			goto error;
	}
	if(_dom && _dom->len > 0) {
		if(shm_str_dup(&c->domain, _dom) < 0)
			goto error;
	}

	if(_ci->received.len > 0) {
		if(shm_str_dup(&c->received, &_ci->received) < 0)
			goto error;
	}
	if(_ci->path && _ci->path->len > 0) {
		if(shm_str_dup(&c->path, _ci->path) < 0)
			goto error;
	}

	LM_DBG("generating hash based on [%.*s]\n", _contact->len, _contact->s);
	c->sl = core_hash(_contact, 0, contact_list->size);
	c->ref_count = 1;
	c->expires = _ci->expires;
	c->q = _ci->q;
	c->sock = _ci->sock;
	c->cseq = _ci->cseq;
	c->flags = _ci->flags;
	c->cflags = _ci->cflags;
	c->methods = _ci->methods;
	c->last_modified = _ci->last_modified;

	return c;
error:
	LM_ERR("no more shm memory\n");
	if(c->path.s)
		shm_free(c->path.s);
	if(c->received.s)
		shm_free(c->received.s);
	if(c->user_agent.s)
		shm_free(c->user_agent.s);
	if(c->callid.s)
		shm_free(c->callid.s);
	if(c->c.s)
		shm_free(c->c.s);
	if(c->domain.s)
		shm_free(c->domain.s);
	if(c->aor.s)
		shm_free(c->aor.s);
	shm_free(c);
	return 0;
}

/*!
 * \brief Free all memory associated with given contact structure
 * \param _c freed contact
 */
void free_ucontact(ucontact_t *_c)
{
	struct ul_callback *cbp, *cbp_tmp;
	struct contact_dialog_data *dialog_data, *tmp_dialog_data;
	param_t *tmp, *tmp1;

	if(!_c)
		return;
	LM_DBG("Freeing ucontact [%p] => [%.*s]\n", _c, _c->c.len, _c->c.s);
	if(_c->path.s)
		shm_free(_c->path.s);
	if(_c->received.s)
		shm_free(_c->received.s);
	if(_c->user_agent.s)
		shm_free(_c->user_agent.s);
	if(_c->callid.s)
		shm_free(_c->callid.s);
	if(_c->c.s)
		shm_free(_c->c.s);

	tmp = _c->params;
	while(tmp) {
		tmp1 = tmp->next;
		if(tmp->body.s)
			shm_free(tmp->body.s);
		if(tmp->name.s)
			shm_free(tmp->name.s);
		if(tmp)
			shm_free(tmp);
		tmp = tmp1;
	}

	if(_c->domain.s)
		shm_free(_c->domain.s);
	if(_c->aor.s)
		shm_free(_c->aor.s);

	//free dialog data
	for(dialog_data = _c->first_dialog_data; dialog_data;) {
		tmp_dialog_data = dialog_data;
		dialog_data = dialog_data->next;
		shm_free(tmp_dialog_data);
	}

	//free callback list
	for(cbp = _c->cbs->first; cbp;) {
		cbp_tmp = cbp;
		cbp = cbp->next;
		if(cbp_tmp->param)
			shm_free(cbp_tmp->param);
		shm_free(cbp_tmp);
	}
	shm_free(_c->cbs);
	lock_dealloc(_c->lock);
	lock_destroy(_c->lock);
	shm_free(_c);
}

/*!
 * \brief Print contact, for debugging purposes only
 * \param _f output file
 * \param _c printed contact
 */
void print_ucontact(FILE *_f, ucontact_t *_c)
{
	time_t t = time(0);
	param_t *tmp;

	fprintf(_f, "\t~~~Contact(%p) (refcount: %d)~~~\n", _c, _c->ref_count);
	fprintf(_f, "\t\tdomain    : '%.*s'\n", _c->domain.len, ZSW(_c->domain.s));
	fprintf(_f, "\t\taor       : '%.*s'\n", _c->aor.len, ZSW(_c->aor.s));
	fprintf(_f, "\t\tContact   : '%.*s'\n", _c->c.len, ZSW(_c->c.s));

	fprintf(_f, "\t\tParams   :\n");
	tmp = _c->params;
	while(tmp) {
		fprintf(_f, "\t\t\tParam Name: '%.*s' Param Body '%.*s'\n",
				tmp->name.len, ZSW(tmp->name.s), tmp->body.len,
				ZSW(tmp->body.s));
		tmp = tmp->next;
	}


	fprintf(_f, "\t\tExpires   : ");
	if(_c->expires == 0) {
		fprintf(_f, "\t\tPermanent\n");
	} else if(_c->expires == UL_EXPIRED_TIME) {
		fprintf(_f, "\t\tDeleted\n");
	} else if(t > _c->expires) {
		fprintf(_f, "\t\tExpired\n");
	} else {
		fprintf(_f, "\t\t%u\n", (unsigned int)(_c->expires - t));
	}
	fprintf(_f, "\t\tq         : %s\n", q2str(_c->q, 0));
	fprintf(_f, "\t\tCall-ID   : '%.*s'\n", _c->callid.len, ZSW(_c->callid.s));
	fprintf(_f, "\t\tCSeq      : %d\n", _c->cseq);
	fprintf(_f, "\t\tUser-Agent: '%.*s'\n", _c->user_agent.len,
			ZSW(_c->user_agent.s));
	fprintf(_f, "\t\treceived  : '%.*s'\n", _c->received.len,
			ZSW(_c->received.s));
	fprintf(_f, "\t\tPath      : '%.*s'\n", _c->path.len, ZSW(_c->path.s));
	fprintf(_f, "\t\tState     : %s\n", get_contact_state_as_string(_c->state));
	fprintf(_f, "\t\tFlags     : %u\n", _c->flags);
	if(_c->sock) {
		fprintf(_f, "\t\tSock      : %.*s (%p)\n", _c->sock->sock_str.len,
				_c->sock->sock_str.s, _c->sock);
	} else {
		fprintf(_f, "\t\tSock      : none (null)\n");
	}
	fprintf(_f, "\t\tMethods   : %u\n", _c->methods);
	fprintf(_f, "\t\tnext      : %p\n", _c->next);
	fprintf(_f, "\t\tprev      : %p\n", _c->prev);
	fprintf(_f, "\t~~~/Contact~~~~\n");
}

/*!
 * \brief Update existing contact in memory with new values
 * \param _c contact
 * \param _ci contact informations
 * \return 0 on success, -1 on failure
 */
int mem_update_ucontact(ucontact_t *_c, ucontact_info_t *_ci)
{
#define update_str(_old, _new)                         \
	do {                                               \
		if((_old)->len < (_new)->len) {                \
			ptr = (char *)shm_malloc((_new)->len);     \
			if(ptr == 0) {                             \
				LM_ERR("no more shm memory\n");        \
				return -1;                             \
			}                                          \
			memcpy(ptr, (_new)->s, (_new)->len);       \
			if((_old)->s)                              \
				shm_free((_old)->s);                   \
			(_old)->s = ptr;                           \
		} else {                                       \
			memcpy((_old)->s, (_new)->s, (_new)->len); \
		}                                              \
		(_old)->len = (_new)->len;                     \
	} while(0)

	char *ptr;

	/* No need to update Callid as it is constant
     * per ucontact (set at insert time)  -bogdan */

	update_str(&_c->user_agent, _ci->user_agent);

	if(_ci->received.s && _ci->received.len) {
		update_str(&_c->received, &_ci->received);
	} else {
		if(_c->received.s)
			shm_free(_c->received.s);
		_c->received.s = 0;
		_c->received.len = 0;
	}

	if(_ci->path) {
		update_str(&_c->path, _ci->path);
	} else {
		if(_c->path.s)
			shm_free(_c->path.s);
		_c->path.s = 0;
		_c->path.len = 0;
	}

	LM_DBG("Setting contact expires to %d which is in %d seconds time\n",
			(unsigned int)_ci->expires,
			(unsigned int)(_ci->expires - time(NULL)));
	_c->sock = _ci->sock;
	_c->expires = _ci->expires;
	_c->q = _ci->q;
	_c->cseq = _ci->cseq;
	_c->methods = _ci->methods;
	_c->last_modified = _ci->last_modified;
	_c->flags = _ci->flags;
	_c->cflags = _ci->cflags;

	return 0;
}

/*!
 * \brief Setting contact expires to now in memory
 * \param _c contact
  * \return 0 on success, -1 on failure
 */
int mem_expire_ucontact(ucontact_t *_c)
{
	get_act_time();
	_c->expires = act_time;

	return 0;
}

/*!
 * \brief Setting ucontact expires to now
 * \param _r record the contact belongs to
 * \param _c updated contact
 * \return 0 on success, -1 on failure
 */
int expire_scontact(struct impurecord *_r, ucontact_t *_c)
{
	/* we have to update memory in any case, but database directly
     * only in db_mode 1 */
	LM_DBG("Expiring contact aor: [%.*s] and contact uri: [%.*s]\n",
			_c->aor.len, _c->aor.s, _c->c.len, _c->c.s);
	if(mem_expire_ucontact(_c) < 0) {
		LM_ERR("failed to update memory\n");
		return -1;
	}

	if(db_mode == WRITE_THROUGH
			&& (db_insert_ucontact(_r, _c)
					!= 0)) { /* this is an insert/update */
		LM_ERR("failed to update contact in DB [%.*s]\n", _c->aor.len,
				_c->aor.s);
		return -1;
	}

	//make sure IMPU is linked to this contact
	link_contact_to_impu(_r, _c, 1);

	/* run callbacks for UPDATE event */
	if(exists_ulcb_type(_c->cbs, UL_CONTACT_EXPIRE)) {
		LM_DBG("exists callback for type= UL_CONTACT_UPDATE\n");
		run_ul_callbacks(_c->cbs, UL_CONTACT_EXPIRE, _r, _c);
	}
	if(exists_ulcb_type(_r->cbs, UL_IMPU_EXPIRE_CONTACT)) {
		run_ul_callbacks(_r->cbs, UL_IMPU_EXPIRE_CONTACT, _r, _c);
	}

	return 0;
}


/*!
 * \brief Update ucontact with new values
 * \param _r record the contact belongs to
 * \param _c updated contact
 * \param _ci new contact informations
 * \return 0 on success, -1 on failure
 */
int update_scontact(struct impurecord *_r, ucontact_t *_c, ucontact_info_t *_ci)
{
	/* we have to update memory in any case, but database directly
     * only in db_mode 1 */
	LM_DBG("Updating contact aor: [%.*s] and contact uri: [%.*s]\n",
			_c->aor.len, _c->aor.s, _c->c.len, _c->c.s);
	if(mem_update_ucontact(_c, _ci) < 0) {
		LM_ERR("failed to update memory\n");
		return -1;
	}

	if(db_mode == WRITE_THROUGH
			&& (db_insert_ucontact(_r, _c)
					!= 0)) { /* this is an insert/update */
		LM_ERR("failed to update contact in DB [%.*s]\n", _c->aor.len,
				_c->aor.s);
		return -1;
	}

	//make sure IMPU is linked to this contact
	link_contact_to_impu(_r, _c, 1);

	/* run callbacks for UPDATE event */
	if(exists_ulcb_type(_c->cbs, UL_CONTACT_UPDATE)) {
		LM_DBG("exists callback for type= UL_CONTACT_UPDATE\n");
		run_ul_callbacks(_c->cbs, UL_CONTACT_UPDATE, _r, _c);
	}
	if(exists_ulcb_type(_r->cbs, UL_IMPU_UPDATE_CONTACT)) {
		run_ul_callbacks(_r->cbs, UL_IMPU_UPDATE_CONTACT, _r, _c);
	}

	//    update_contact_pos(_r, _c);

	return 0;
}


/*!
 * \brief Add dialog data to contact
 * used when this contact is part of a confirmed dialog so we can tear down the dialog if the contact is removed
 */
int add_dialog_data_to_contact(
		ucontact_t *_c, unsigned int h_entry, unsigned int h_id)
{

	struct contact_dialog_data *dialog_data =
			(struct contact_dialog_data *)shm_malloc(
					sizeof(struct contact_dialog_data));

	LM_DBG("Adding dialog data to contact <%.*s> with h_entry <%d> and h_id "
		   "<%d>",
			_c->c.len, _c->c.s, h_entry, h_id);

	memset(dialog_data, 0, sizeof(struct contact_dialog_data));

	dialog_data->h_entry = h_entry;
	dialog_data->h_id = h_id;
	dialog_data->next = 0;
	dialog_data->prev = 0;

	if(_c->first_dialog_data == 0) {
		//first entry in the list
		_c->first_dialog_data = dialog_data;
		_c->last_dialog_data = dialog_data;
	} else {
		//not first entry in list
		_c->last_dialog_data->next = dialog_data;
		dialog_data->prev = _c->last_dialog_data;
		_c->last_dialog_data = dialog_data;
	}

	return 0;
}

/*!
 * \brief Add dialog data to contact
 * used when this contact is part of a confirmed dialog so we can tear down the dialog if the contact is removed
 */
int remove_dialog_data_from_contact(
		ucontact_t *_c, unsigned int h_entry, unsigned int h_id)
{
	struct contact_dialog_data *dialog_data, *tmp_dialog_data;
	LM_DBG("Removing dialog data from contact <%.*s> with h_entry <%d> and "
		   "h_id <%d>",
			_c->c.len, _c->c.s, h_entry, h_id);

	for(dialog_data = _c->first_dialog_data; dialog_data;) {
		tmp_dialog_data = dialog_data;
		dialog_data = dialog_data->next;
		if(tmp_dialog_data->h_entry == h_entry
				&& tmp_dialog_data->h_id == h_id) {
			LM_DBG("Found matching dialog data so will remove it");
			if(tmp_dialog_data->prev) {
				tmp_dialog_data->prev->next = tmp_dialog_data->next;
			} else {
				_c->first_dialog_data = tmp_dialog_data->next;
			}
			if(tmp_dialog_data->next) {
				tmp_dialog_data->next->prev = tmp_dialog_data->prev;
			} else {
				_c->last_dialog_data = tmp_dialog_data->prev;
			}
			shm_free(tmp_dialog_data);
			return 0;
		}
	}

	LM_DBG("Did not find dialog data to remove from contact");
	return 1;
}

void release_scontact(struct ucontact *_c)
{
	lock_contact_slot_i(_c->sl);
	unref_contact_unsafe(_c);
	unlock_contact_slot_i(_c->sl);
}
